package ru.aslanov.schedule.server.gcalendar;

import com.google.appengine.api.taskqueue.TaskAlreadyExistsException;
import com.google.gdata.data.batch.BatchOperationType;
import com.google.gdata.data.batch.BatchStatus;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.calendar.CalendarEventFeed;
import com.google.gdata.model.batch.BatchUtils;
import ru.aslanov.schedule.model.*;
import ru.aslanov.schedule.server.InputLangUtil;
import ru.aslanov.schedule.server.PMF;
import ru.aslanov.schedule.shared.SyncStatus;
import ru.aslanov.schedule.utils.ScheduleUtil;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

/**
 * Created by IntelliJ IDEA.
 * Created: Mar 10, 2010 1:33:57 PM
 *
 * @author Sergey Aslanov
 */
public class GCalendarSyncEventsTaskServlet extends HttpServlet {
    private static final Logger log = Logger.getLogger(GCalendarSyncEventsTaskServlet.class.getName());

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        final int MAX_TIME = 9 * 60 * 1000; // 9 minutes (of 10 allowed)
        long started = System.currentTimeMillis();

        log.info("Executing gcal-sync-events task");

        final String scheduleKey = request.getParameter("schedule");
        final PersistenceManager pm = PMF.getThreadLocalPersistenceManager();
        final Transaction tran = pm.currentTransaction();

        final Query query = pm.newQuery(GoogleCalendarSyncEvent.class, "scheduleKey == :s");
        query.setOrdering("time, num");

        Schedule schedule = pm.getObjectById(Schedule.class, scheduleKey);
        String error = null;
        boolean finished = true;
        
        final GoogleCalendarSync googleCalendarSync = schedule.getGoogleCalendarSync();
        if (googleCalendarSync == null || googleCalendarSync.getSessionToken() == null) {
            error = "Calendar authentiaction is not set, returning";
            log.warning(error);
            return;
        }

        InputLangUtil.setThreadLocalInputLang(schedule.getDefaultInputLanguage(), schedule.getDefaultInputLanguage());
        try {
            final GCalendarEventGenerator calendarEventGenerator = GCalendarEventGenerator.getInstance(getServletContext());
            GCalendarConnectService gCalendarConnectService = new GCalendarConnectService(scheduleKey);

            final Map<String, ScheduleItem> scheduleItemsToUpdateMap = new HashMap<String, ScheduleItem>();

            String prevCalId = null;

            String defaultCity = ScheduleUtil.getDefaultCity(schedule);

            Collection res = pm.detachCopyAll((Collection) query.execute(scheduleKey));

            for (Object o : res) {
                if ((System.currentTimeMillis() - started) >= MAX_TIME) {
                    finished = false;
                    break;
                }

                final GoogleCalendarSyncEvent syncEvent = (GoogleCalendarSyncEvent) o;

                final String calId = syncEvent.getGoogleCalendarId();
                if (!calId.equals(prevCalId)) {
                    if (prevCalId != null) {
                        gCalendarConnectService.sendBatch();
                    }
                    log.info("Processing calendar " + calId);
                    gCalendarConnectService.startBatch(calId, new GCalendarConnectService.BatchResultProcessor() {
                        @Override
                        public String processBatchResultEntry(CalendarEventFeed batchResponse) {
                            String lastError = null;

                            for (CalendarEventEntry batchResultEntry : batchResponse.getEntries()) {
                                BatchStatus status = com.google.gdata.data.batch.BatchUtils.getBatchStatus(batchResultEntry);
                                String batchId = BatchUtils.getBatchId(batchResultEntry);
                                if (BatchUtils.isSuccess(batchResultEntry)) {
                                    final ScheduleItem scheduleItem = scheduleItemsToUpdateMap.get(batchId);
                                    if (scheduleItem != null) {
                                        tran.begin();
                                        scheduleItem.setGoogleEventId(
                                                BatchUtils.getBatchOperationType(batchResultEntry) != BatchOperationType.DELETE
                                                        ? batchResultEntry.getId() : null);
                                        tran.commit();
                                    } else {
                                        if (BatchUtils.getBatchOperationType(batchResultEntry) != BatchOperationType.DELETE)
                                            log.warning("Cannot find schedule item to update: " + batchId);
                                    }
                                } else {
                                    if (BatchUtils.getBatchOperationType(batchResultEntry) == BatchOperationType.DELETE
                                            && (status.getCode() == 404 || status.getCode() == 400 || status.getCode() == 403)) {
                                        log.warning("Cannot delete event: " + status.getReason() + " (code=" + status.getCode() + ") ID=" + batchId);
                                    } else {
                                        lastError = "(operation=" + BatchUtils.getBatchOperationType(batchResultEntry)
                                                + ", code=" + status.getCode() 
                                                + ", reason=" + status.getReason()
                                                + ", content=" + status.getContent() + ")";
                                        log.warning(batchId + " failed " + lastError);
                                    }
                                }
                            }

                            return lastError;
                        }
                    });
                    prevCalId = calId;
                }

                if (syncEvent.getOperation() == Operation.UPDATE || syncEvent.getOperation() == Operation.CREATE) {
                    Group group = syncEvent.getGroup().getObject(pm);
                    final ScheduleItem scheduleItem = syncEvent.getScheduleItem().getObject(pm);
                    Collection<ScheduleItem> scheduleItems = null;
                    if (group != null) {
                        scheduleItems = group.getScheduleItems();
                    } else if (scheduleItem != null) {
                        scheduleItems = Collections.singletonList(scheduleItem);
                        group = scheduleItem.getGroup();
                    }
                    if (scheduleItems != null) {
                        for (ScheduleItem item : scheduleItems) {
                            final String googleEventId = item.getGoogleEventId();
                            if (syncEvent.getOperation() == Operation.UPDATE && googleEventId != null) {
                                CalendarEventEntry deleteEntry = new CalendarEventEntry();
                                deleteEntry.setId(googleEventId);
                                gCalendarConnectService.addBatchEntry(deleteEntry, googleEventId, BatchOperationType.DELETE);
                            }

                            scheduleItemsToUpdateMap.put(item.getEncodedKey(), item);
                            final CalendarEventEntry calendarEventEntry = calendarEventGenerator.generateEvent(item, group, defaultCity, pm);
                            gCalendarConnectService.addBatchEntry(calendarEventEntry, item.getEncodedKey(), BatchOperationType.INSERT);
                        }
                    }
                } else if (syncEvent.getOperation() == Operation.REMOVE) {
                    CalendarEventEntry deleteEntry = new CalendarEventEntry();
                    deleteEntry.setId(syncEvent.getGoogleEventId());
                    String batchId = null;
                    try {
                        ScheduleItem scheduleItem = syncEvent.getScheduleItem().getObject(pm);
                        if (scheduleItem != null) {
                            scheduleItemsToUpdateMap.put(scheduleItem.getEncodedKey(), scheduleItem);
                            batchId = scheduleItem.getEncodedKey();
                        }
                    } catch (JDOObjectNotFoundException e) { }

                    if (batchId == null) 
                        batchId = syncEvent.getGoogleEventId();

                    gCalendarConnectService.addBatchEntry(deleteEntry, batchId, BatchOperationType.DELETE);
                }

                pm.deletePersistent(pm.getObjectById(GoogleCalendarSyncEvent.class, syncEvent.getEncodedKey()));
            }

            gCalendarConnectService.sendBatch();

            if (!finished) {
                try {
                    log.warning("Task is running longer than " + MAX_TIME/1000 + " sec. Creating one more task");
                    GCalendarSyncService.enqueEventsSync(scheduleKey);
                } catch (TaskAlreadyExistsException e) {
                    throw new ServletException(e);
                }
            } else {
                log.info("SYNC FINISHED SUCCESSFULY");
            }
        } catch (Throwable e) {
            if (tran.isActive()) tran.rollback();
            log.log(java.util.logging.Level.SEVERE, "Sync error", e);
            //throw new ServletException(e);
            error = e.toString();
            GCalendarSyncService.clearSyncQueue(pm, schedule.getEncodedKey());
        } finally {
            InputLangUtil.clearThreadLocalInputLang();
            if (tran.isActive()) tran.rollback();
            query.closeAll();
            if ((finished || error != null) && schedule != null) {
                GCalendarSyncService.storeSyncResult(schedule, error, SyncStatus.FINISHED);
            }
        }
    }
}
